package pl.ydp.components.mathcell.mathcell.expressions
{
	import pl.ydp.components.mathcell.mathrecognizer.characters.Character;
	import pl.ydp.components.mathcell.mathrecognizer.characters.CharacterType;
	
	/**
	 * Manages the expression in the cell.
	 * 
	 * All the functionalities concerning the expressions
	 * are concentrated in this class.
	 */
	public class CellExpressionManager
	{
		private var charsReplacements:Object;
		
		public function CellExpressionManager()
		{
			clearExpressionUser();
			defaultFilled = false;
			expressionFixed = "";
			expressionExpected = "";
			expressionSuggested = "";
			allowFractions = false;
			onlyFraction = false;
			allowFractionReduction = false;
			maximumLength = 1;
			initDefaultCharsReplacements();
		}
		
		// ------------------------- PROPERTIES ---------------------------
		
		private var _allowFractions:Boolean;
		
		public function set allowFractions(af:Boolean):void{
			_allowFractions = af;
		}
		
		public function get allowFractions():Boolean{
			return _allowFractions;
		}
		
		
		private var _onlyFraction:Boolean;
		
		public function set onlyFraction(of:Boolean):void{
			_onlyFraction = of;
		}
		
		public function get onlyFraction():Boolean{
			return _onlyFraction;
		}
		
		private var _allowFractionReduction:Boolean;
		
		public function set allowFractionReduction(afr:Boolean):void{
			_allowFractionReduction = afr;
		}
		
		public function get allowFractionReduction():Boolean{
			return _allowFractionReduction;
		}
		
		
		private var _maximumLength:Number;
		
		public function set maximumLength(ml:Number):void{
			_maximumLength = ml;
		}
		
		public function get maximumLength():Number{
			
			var expressionLength:Number = MathExpressionProcessor.findExpressionLength(bindingExpression);
			
			if (expressionLength < 1)
				expressionLength = 1;
				
			if (_maximumLength >  expressionLength)
				expressionLength = _maximumLength;
			
			return expressionLength;
		}
		
		// EXPRESSION FIXED
		
		private var _defaultFilled:Boolean;
		
		public function set defaultFilled(df:Boolean):void{
			_defaultFilled = df;
		}
		
		public function get defaultFilled():Boolean{
			return _defaultFilled;
		}
		
		// EXPRESSION FIXED
		
		private var _expressionFixed:String;
		
		public function set expressionFixed(ef:String):void{
			_expressionFixed = validateExpression(ef, true);
		}
		
		public function get expressionFixed():String{
			return _expressionFixed;
		}
			
		// EXPRESSION EXPECTED	
				
		private var _expressionExpected:String;
		
		public function set expressionExpected(ee:String):void{
			_expressionExpected = validateExpression(ee, true);
		}
		
		public function get expressionExpected():String{
			return _expressionExpected;
		}
		
		// EXPRESSION SUGGESTED
		
		private var _expressionSuggested:String;
		
		public function set expressionSuggested(es:String):void{
			_expressionSuggested = validateExpression(es, true);
		}
		
		public function get expressionSuggested():String{
			return _expressionSuggested;
		}
		
		
		
		private var _expressionUser:String;
		
		private function set expressionUserW(eu:String):void{
			_expressionUser = eu;
		}
		
		private function get expressionUserW():String{
			return _expressionUser;
		}
		
		public function get expressionUser():String{
			return _expressionUser;
		}
		
		
		// ------------------------- PUBLIC METHODS ---------------------------
		
		/**
		 * Appends the contents to the current expression of the user
		 * 
		 * @return true if at least one character of the expression has been accepted
		 * otherwise false 
		 **/
		public function appendExpressionUser(expr:String):Boolean{
			
			var expressionValidated:String = validateExpression(expr);
			
			if (expressionValidated.length > 0){
				
				if (maximumLength == 1 && !allowFractions){
					expressionUserW = expressionValidated.substr(0, 1);
				}else {
					for each (var currChar:String in expressionValidated.split("")){
						inputExpressionUserCharacter(currChar);
					}
				}
				
				return true;
			}
			
			return false;
		}
		
		public function inputExpressionUserCharacter(char:String):Boolean{
			
			char = replaceIfReplacementExists( char );
			
			if (char.length < 1)
				return false;
				
			var charValidated:String = validateExpression(char.substr(0, 1));
			
			if (charValidated.length < 1)
				return false;
				
			if (maximumLength == 1 &&  !allowFractions){
				expressionUserW = charValidated;
			} else {
				var lastExpressionUserChar:String = expressionUserW.substr(expressionUserW.length-1, 1);
				
				// if | is followed by |, the | should be neutralized
				if (lastExpressionUserChar == MathExpressionProcessor.OPERATOR_FRACTION  &&  
					charValidated == MathExpressionProcessor.OPERATOR_FRACTION){
					removeLastCharacterExpressionUser();
				} else {
					var outputExpression:String = expressionUserW + charValidated;
					if (onlyFraction)
						outputExpression = MathExpressionProcessor.OPERATOR_FRACTION + outputExpression + MathExpressionProcessor.OPERATOR_FRACTION;
					if (MathExpressionProcessor.findExpressionLength(outputExpression) <= maximumLength){
						expressionUserW += charValidated;
					} else {
						return false;
					}
				}
			}
			
			
			return true;
		}
		
		public function setExpressionUser(expr:String):Boolean{
			clearExpressionUser();
			return appendExpressionUser(expr);
		}
		
		public function clearExpressionUser():void{
			_expressionUser = "";
		}
		
		public function removeLastCharacterExpressionUser():void{
			if (_expressionUser.length > 0)
				_expressionUser = _expressionUser.substring(0, _expressionUser.length-1);
		}
		
		public function reset():void{
			if (defaultFilled){
				if (expressionExpected.length > 0)
					setExpressionUser(expressionExpected);
				else if (expressionSuggested.length > 0)
					setExpressionUser(expressionSuggested);
			} else {
				clearExpressionUser();
			}
		}
		
		public function get expressionContent():String{
			if (bindingExpressionType == CellBindingExpressionType.FIXED){
				return expressionFixed;
			}
			
			var _expressionContent:String = _expressionUser;			
			
			if (onlyFraction  &&  allowFractions  &&  _expressionContent.length > 0)
				_expressionContent = MathExpressionProcessor.OPERATOR_FRACTION + _expressionContent + MathExpressionProcessor.OPERATOR_FRACTION;
			
			return _expressionContent;
		}
		
		
		public function getExpressionDisplay(showingAnswers:Boolean = false):String{
			var bet:Number = bindingExpressionType;
			var expressionToDisplay:String = ""; 
			
			if (bet == CellBindingExpressionType.FIXED){
				expressionToDisplay =  expressionFixed;
			} else if (!showingAnswers){
				expressionToDisplay = expressionContent;
			} else if (showingAnswers  &&  bet == CellBindingExpressionType.EXPECTED){
				expressionToDisplay = expressionExpected;
			} else if (showingAnswers  &&  bet == CellBindingExpressionType.SUGGESTED){
				expressionToDisplay = expressionSuggested;
			}
			
			while (expressionToDisplay.indexOf("-") != -1)
				expressionToDisplay = expressionToDisplay.replace("-", "−");
			
			return expressionToDisplay;
		}
		
		public function getResult():Boolean{
			var bet:Number = bindingExpressionType;
			
			if (bet == CellBindingExpressionType.FIXED){
				return true
			} else if (bet == CellBindingExpressionType.EXPECTED){
				return MathExpressionProcessor.compare(expressionContent, expressionExpected, (allowFractionReduction && allowFractions));
			}
			
			return false;
		}
		
		public function get bindingExpressionType():Number{
			if (expressionFixed.length > 0)
				return CellBindingExpressionType.FIXED;
			else if (expressionExpected.length > 0)
				return CellBindingExpressionType.EXPECTED;
			else if (expressionSuggested.length > 0)
				return CellBindingExpressionType.SUGGESTED;
				
			return CellBindingExpressionType.NONE;
		}
		
		// ------------------------- PRIVATE METHODS ---------------------------
		
		private function get bindingExpression():String{
			if (expressionFixed.length > 0)
				return expressionFixed;
			else if (expressionExpected.length > 0)
				return expressionExpected;
			else if (expressionSuggested.length > 0)
				return expressionSuggested;
			
			return ""; 
		}
		
		private function validateExpression(expr:String, leaveFractions:Boolean = false):String{
			
			var currChar:Character;
			var checkedExpr:String = "";
			
			for (var i:Number = 0 ; i < expr.length ; i++){
			 	currChar = new Character(expr.charAt(i));
			 	if (currChar.type != CharacterType.UNKNOWN  &&  
					(currChar.symbol != MathExpressionProcessor.OPERATOR_FRACTION  ||  (allowFractions && !onlyFraction) ||  leaveFractions)  && 
						(!onlyFraction  ||  leaveFractions  ||  currChar.type == CharacterType.DIGIT  ||  
						((currChar.code == Character.DIVIDE  ||  currChar.code == Character.DIVIDE_G  ||  currChar.code == Character.DIVIDE_S)  &&  !checkExpressionContainsDivideOperator())
					) ){
			 		checkedExpr += expr.charAt(i);
			 	}
			 }
			 
			 return checkedExpr;
		}
		
		private function checkExpressionContainsDivideOperator():Boolean{
			return 	expressionUserW.indexOf(String.fromCharCode(Character.DIVIDE)) != -1 ||
					expressionUserW.indexOf(String.fromCharCode(Character.DIVIDE_G)) != -1 ||
					expressionUserW.indexOf(String.fromCharCode(Character.DIVIDE_S)) != -1;
		}
		
		
		private function replaceIfReplacementExists(char:String):String
		{
			if( charsReplacements.hasOwnProperty( char ) ){
				char = charsReplacements[ char ];
			}
			return char;
		}
		
		private function initDefaultCharsReplacements():void
		{
			charsReplacements = {};
			charsReplacements['*'] = '·';
			charsReplacements['/'] = ':';
		}
		
		public function updateCharsReplacements( newReplacements:String ):void
		{
			if( newReplacements != null ){
				var replacementsObject:Object = getReplacementsMapFromString( newReplacements );
				for ( var key:String in replacementsObject ){
					charsReplacements[ key ] = replacementsObject[ key ];
				}
			}
		}
		
		private function getReplacementsMapFromString(newOverrides:String):Object
		{
			var replacementsArray:Array = newOverrides.split(' ').join('').split('|');
			var replacementsMap:Object = {};
			for ( var i:int = 0; i+1 < replacementsArray.length; i+=2 ){
				var overrideKey:String = replacementsArray[ i ];
				var overrideValue:String = replacementsArray[ i+1 ];
				replacementsMap[ overrideKey ] = overrideValue;
			}
			return replacementsMap;
		}
	}
}